// Copyright 2001-2006, FreeHEP.
package org.freehep.graphicsio.svg;

import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import org.freehep.graphicsio.font.FontTable;

/**
 * A table to remember which glyphs were used while writing a svg file. Entries
 * are added by calling {@link #addGlyphs(String, java.awt.Font)}. The final SVG
 * tag for the &lt;defs&gt; section is generated using {@link #toString()}. Use
 * {@link #normalize(java.util.Map)} for referencing embedded glyphs in
 * &lt;text&gt; tags.
 *
 * @author Steffen Greiffenberg
 * @version $Id: SVGFontTable.java,v 1.6 2009-11-11 17:05:23 hohenwarter Exp $
 */
public class SVGFontTable {

	/**
	 * Stores fonts and a glyph-hashtable. The font key ist normalized using
	 * {@link #untransform(java.awt.Font)}
	 */
	private Hashtable <Font, Hashtable<String, SVGGlyph>> glyphs = new Hashtable<>();

	/**
	 * creates a glyph for the string character
	 *
	 * @param c
	 * @param font
	 * @return unique font name
	 */
	private SVGGlyph addGlyph(int c, Font font) {
		// is the font stored?
		Hashtable/* <String, SVGGlyph> */ glyphs = getGlyphs(font);

		// does a glyph already exist?
		SVGGlyph result = (SVGGlyph) glyphs.get(String.valueOf(c));

		// create a new one?
		if (result == null) {
			// create and store the SVG Glyph
			result = createGlyph(c, font);
			glyphs.put(String.valueOf(c), result);
		}

		return result;
	}

	/**
	 * @param c
	 * @param font
	 * @return GlyphVector using a default rendering context
	 */
	private static SVGGlyph createGlyph(int c, Font font) {
		GlyphVector glyphVector = font.createGlyphVector(
				// flipping is done by SVGGlyph
				new FontRenderContext(null, true, true),
				// unicode to char
				String.valueOf((char) c));

		// create and store the SVG Glyph
		return new SVGGlyph(glyphVector.getGlyphOutline(0), c,
				glyphVector.getGlyphMetrics(0));
	}

	/**
	 * creates the glyph for the string
	 *
	 * @param string
	 * @param font
	 */
	protected void addGlyphs(String string, Font font) {
		font = untransform(font);

		// add characters
		for (int i = 0; i < string.length(); i++) {
			addGlyph(string.charAt(i), font);
		}
	}

	/**
	 * @param font
	 * @return glyph vectors for font
	 */
	private Hashtable/* <String SVGGlyph> */ getGlyphs(Font font) {
		// derive a default font for the font table
		font = untransform(font);

		Hashtable/* <String SVGGlyph> */ result = (Hashtable/*
															 * <String SVGGlyph>
															 */) glyphs
				.get(font);
		if (result == null) {
			result = new Hashtable/* <String SVGGlyph> */();
			glyphs.put(font, result);
		}
		return result;
	}

	/**
	 * creates the font entry:
	 * 
	 * <PRE>
	 * &lt;font&gt;
	 * &lt;glyph ... /&gt;
	 * ...
	 * &lt;/font&gt;
	 * </PRE>
	 *
	 * @return string representing the entry
	 */
	@Override
	public String toString() {
		StringBuffer result = new StringBuffer();

		Enumeration/* <Font> */ fonts = this.glyphs.keys();
		while (fonts.hasMoreElements()) {
			Font font = (Font) fonts.nextElement();

			// replace font family for svg
			Map /* <TextAttribute, ?> */ attributes = font.getAttributes();

			// Dialog -> Helvetica
			normalize(attributes);

			// familiy
			result.append("<font id=\"");
			result.append(attributes.get(TextAttribute.FAMILY));
			// added horiz-adv-x="0" as it's compulsory according to
			// http://validator.w3.org/check
			result.append("\" horiz-adv-x=\"0\" >\n");

			// font-face
			result.append("<font-face font-family=\"");
			result.append(attributes.get(TextAttribute.FAMILY));
			result.append("\" ");

			// bold
			if (TextAttribute.WEIGHT_BOLD
					.equals(attributes.get(TextAttribute.WEIGHT))) {
				result.append("font-weight=\"bold\" ");
			} else {
				result.append("font-weight=\"normal\" ");
			}

			// italic
			if (TextAttribute.POSTURE_OBLIQUE
					.equals(attributes.get(TextAttribute.POSTURE))) {
				result.append("font-style=\"italic\" ");
			} else {
				result.append("font-style=\"normal\" ");
			}

			// size
			Float size = (Float) attributes.get(TextAttribute.SIZE);
			result.append("font-size=\"");
			result.append(SVGGraphics2D.fixedPrecision(size.floatValue()));
			result.append("\" ");

			// number of coordinate units on the em square,
			// the size of the design grid on which glyphs are laid out
			result.append("units-per-em=\"");
			result.append(SVGGraphics2D.fixedPrecision(SVGGlyph.FONT_SIZE));
			result.append("\" ");

			TextLayout tl = new TextLayout("By", font,
					new FontRenderContext(new AffineTransform(), true, true));

			// The maximum unaccented height of the font within the font
			// coordinate system.
			// If the attribute is not specified, the effect is as if the
			// attribute were set
			// to the difference between the units-per-em value and the
			// vert-origin-y value
			// for the corresponding font.
			result.append("ascent=\"");
			result.append(tl.getAscent());
			result.append("\" ");

			// The maximum unaccented depth of the font within the font
			// coordinate system.
			// If the attribute is not specified, the effect is as if the
			// attribute were set
			// to the vert-origin-y value for the corresponding font.
			result.append("descent=\"");
			result.append(tl.getDescent());
			result.append("\" ");

			// For horizontally oriented glyph layouts, indicates the alignment
			// coordinate for glyphs to achieve alphabetic baseline alignment.
			// result.append("alphabetic=\"0\"

			// close "<font-face"
			result.append("/>\n");

			// missing glyph
			SVGGlyph glyph = createGlyph(font.getMissingGlyphCode(), font);
			result.append("<missing-glyph ");
			result.append(glyph.getHorizontalAdvanceXString());
			result.append(" ");
			result.append(glyph.getPathString());
			result.append("/>\n");

			// regular glyphs
			Iterator glyphs = getGlyphs(font).values().iterator();
			while (glyphs.hasNext()) {
				result.append(glyphs.next().toString());
				result.append("\n");
			}

			// close "<font>"
			result.append("</font>\n");
		}

		return result.toString();
	}

	/**
	 * creates a font based on the parameter. The size will be
	 * {@link SVGGlyph#FONT_SIZE} and transformation will be removed. Example:
	 * <BR>
	 * <code>java.awt.Font[family=SansSerif,name=SansSerif,style=plain,size=30]</code>
	 * <BR>
	 * will result to:<BR>
	 * <code>java.awt.Font[family=SansSerif,name=SansSerif,style=plain,size=100]</code>
	 * <BR>
	 * <BR>
	 *
	 * This method does not substitute font name or family.
	 *
	 * @param font
	 * @return font based on the parameter
	 */
	private static Font untransform(Font font) {
		// replace font family
		Map /* <TextAttribute, ?> */ attributes = font.getAttributes();

		// set default font size
		attributes.put(TextAttribute.SIZE, new Float(SVGGlyph.FONT_SIZE));

		// remove font transformation
		attributes.remove(TextAttribute.TRANSFORM);
		attributes.remove(TextAttribute.SUPERSCRIPT);

		return new Font(attributes);
	}

	/**
	 * font replacements makes SVG in AdobeViewer look better, firefox replaces
	 * all font settings, even the family fame
	 */
	private static final Properties replaceFonts = new Properties();
	static {
		replaceFonts.setProperty("dialog", "Helvetica");
		replaceFonts.setProperty("dialoginput", "Courier New");
		// FIXME: works well on windows, others?
		// "TimesRoman" is not valid under Firefox 1.5
		replaceFonts.setProperty("serif", "Times");
		replaceFonts.setProperty("timesroman", "Times");
		replaceFonts.setProperty("sansserif", "Helvetica");
		// FIXME: works well on windows, others?
		// "Courier" is not valid under Firefox 1.5
		replaceFonts.setProperty("monospaced", "Courier New");
		// FIXME: replacement for zapfdingbats?
		replaceFonts.setProperty("zapfdingbats", "Wingdings");
	}

	/**
	 * Replaces TextAttribute.FAMILY by values of replaceFonts. When a font
	 * created using the result of this method the transformation would be:
	 *
	 * <code>java.awt.Font[family=SansSerif,name=SansSerif,style=plain,size=30]</code>
	 * <BR>
	 * will result to:<BR>
	 * <code>java.awt.Font[family=SansSerif,name=Helvetica,style=plain,size=30]</code>
	 * <BR>
	 * <BR>
	 *
	 * Uses {@link FontTable#normalize(java.util.Map)} first.
	 *
	 * @param attributes
	 *            with font name to change
	 */
	public static void normalize(Map /* <TextAttribute, ?> */ attributes) {
		// dialog.bold -> Dialog with TextAttribute.WEIGHT_BOLD
		FontTable.normalize(attributes);

		// get replaced font family name (Yes it's right, not the name!)
		String family = replaceFonts.getProperty(
				((String) attributes.get(TextAttribute.FAMILY)).toLowerCase());
		if (family == null) {
			family = (String) attributes.get(TextAttribute.FAMILY);
		}

		// replace the family (Yes it's right, not the name!) in the attributes
		attributes.put(TextAttribute.FAMILY, family);
	}
}
